/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "../commonheaders.h"
#include "classExImContactBase.h"

namespace NExImport {

static HANDLE CListFindGroup( LPCSTR pszGroup )
{
	CHAR buf[4];
	BYTE i;
	DBVARIANT dbv;

	for( i = 0; i < 255; i++ ) {
		_itoa( i, buf, 10 );
		if( DB::Setting::GetUString( NULL, "CListGroups", buf, &dbv ) )
			break;
		if( dbv.pszVal && pszGroup && !_stricmp( dbv.pszVal + 1, pszGroup ) ) {
			DB::Variant::Free( &dbv );
			return ( HANDLE )( i+1 );
		}
		DB::Variant::Free( &dbv );
	}
	return INVALID_HANDLE_VALUE;
}

/**
 * name:	CExImContactBase
 * class:	CExImContactBase
 * desc:	default constructor
 * param:	none
 * return:	nothing
 **/
CExImContactBase::CExImContactBase()
{
	_isNewContact = FALSE;
	_hContact = INVALID_HANDLE_VALUE;
	ZeroMemory( &_dbvUID, sizeof( DBVARIANT ) );
}

/**
 * name:	~CExImContactBase
 * class:	CExImContactBase
 * desc:	default denstructor
 * param:	none
 * return:	nothing
 **/
CExImContactBase::~CExImContactBase()
{
	DB::Variant::Free( &_dbvUID );
}

/**
 * name:	fromDB
 * class:	CExImContactBase
 * desc:	get contact information from database
 * param:	hContact	- handle to contact whose information to read
 * return:	TRUE if successful or FALSE otherwise
 **/
BOOLEAN CExImContactBase::fromDB( HANDLE hContact )
{
	BOOLEAN ret = FALSE;
	LPSTR szBaseProto;
	DBVARIANT dbv;

	_hContact = hContact;

	_strProto = NULL;
	_strNick = NULL;
	_strDisp = NULL;
	_strGroup = NULL;
	_strUIDKey = NULL;
	DB::Variant::Free( &_dbvUID );
	if( !_hContact ) return TRUE;

	
	szBaseProto = DB::Contact::Proto( _hContact );

	// MS_PROTO_GETCONTACTBASEPROTO fails because the protocol is no longer installed 
	if( szBaseProto == NULL || ( INT )szBaseProto == CALLSERVICE_NOTFOUND ) {
		CHAR proto[128];

		// try to get raw protocol name
		if( !DB::Setting::GetStatic( _hContact, "Protocol", "p", proto, sizeof( proto ) ) ) {
			_strProto = proto;

			// nickname
			if( !DB::Setting::GetUString( _hContact, szBaseProto, SET_CONTACT_NICK, &dbv ) ) {
				_strNick = dbv.pszVal;
				DB::Variant::Free( &dbv );
				ret = TRUE;
			}
		}
	}
	else {
		LPCSTR uidSetting;

		// protocol
		_strProto = szBaseProto;

		// nickname
		if( !DB::Setting::GetUString( _hContact, szBaseProto, SET_CONTACT_NICK, &dbv ) ) {
			_strNick = dbv.pszVal;
			DB::Variant::Free( &dbv );
		}

		// unique id
		uidSetting = ( LPCSTR )CallProtoService( szBaseProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0 );
		if( uidSetting != NULL && ( INT )uidSetting != CALLSERVICE_NOTFOUND ) {
			_strUIDKey = (LPSTR) uidSetting;
			if( !DB::Setting::GetUString( _hContact, szBaseProto, uidSetting, &_dbvUID ) ) {
				ret = TRUE;
			}
		}
	}
	if( _hContact && ret ) {
		if( !DB::Setting::GetUString( _hContact, MOD_CLIST, "Group", &dbv ) ) {
			_strGroup = dbv.pszVal;
			DB::Variant::Free( &dbv );
		}
		if( !DB::Setting::GetUString( _hContact, MOD_CLIST, SET_CONTACT_MYHANDLE, &dbv ) ) {
			_strDisp = dbv.pszVal;
			DB::Variant::Free( &dbv );
		}
	}
	return ret;
}

/**
 * name:	fromIni
 * class:	CExImContactBase
 * desc:	get contact information from a row of a ini file
 * param:	row	- the rows data
 * return:	TRUE if successful or FALSE otherwise
 **/
BOOLEAN CExImContactBase::fromIni( LPSTR& row )
{
	LPSTR p1, p2 = NULL;
	LPSTR pszUIDValue, pszUIDSetting, pszProto = NULL;
	LPSTR pszBuf = &row[0];
	DWORD cchBuf = strlen(row);

	// read uid value
	if( cchBuf > 10 && ( p1 = mir_strrchr( pszBuf, '*{') ) && ( p2 = mir_strchr( p1, '}*') ) && p1 + 2 < p2 ) {
		pszUIDValue = p1 + 1;
		*p1 = *( p2 - 1 ) = 0;

		// insulate the uid setting from buffer pointer
		if( cchBuf > 0 && ( p1 = mir_strrchr( pszBuf, '*<') ) && ( p2 = mir_strchr( p1, '>*') ) && p1 + 2 < p2 ) {
			pszUIDSetting = p1 + 1;
			*p1 = *( p2 - 1 ) = 0;

			// insulate the protocol name from buffer pointer
			if( cchBuf > 0 && ( p1 = mir_strrchr( pszBuf, '*(') ) && ( p2 = mir_strchr( p1, ')*') ) && p1 + 2 < p2 ) {
				pszProto = p1 + 2;
				*( --p1 ) = *( p2 - 2 ) = 0;

				if( strspn( pszUIDValue, "0123456789" ) == mir_strlen( pszUIDValue ) ) {
					_dbvUID.dVal = atol( pszUIDValue );
					_dbvUID.type = DBVT_DWORD;
				}
				else {
					_dbvUID.pszVal = mir_strdup( pszUIDValue );
					_dbvUID.type = DBVT_UTF8;
				}
				_strUIDKey = mir_strdup(pszUIDSetting);
				_strProto = mir_strdup(pszProto);
			}
		}
	}
	// create valid nickname
	_strNick = mir_strdup(pszBuf);
	size_t i = strlen(_strNick);

	while( i > 0 && ( _strNick[i] == ' ' || _strNick[i] == '\t') ) {
		_strNick[i] = 0;
		i--;
	}
	return FALSE;
}

/**
 * name:	toDB
 * class:	CExImContactBase
 * desc:	searches the database for a contact representing the one
 *			identified by this class or creates a new one if it was not found
 * param:	hMetaContact - a meta contact to add this contact to
 * return:	handle of the contact if successful
 **/
HANDLE CExImContactBase::toDB()
{
	DBCONTACTWRITESETTING cws;
	
	// create new contact if none exists
	if( _hContact == INVALID_HANDLE_VALUE && proto() && uidk() && _dbvUID.type != DBVT_DELETED ) {
		// create new contact
		_hContact = ( HANDLE )CallService( MS_DB_CONTACT_ADD, 0, 0 );
		if( !_hContact ) {
			_hContact = INVALID_HANDLE_VALUE;
			return INVALID_HANDLE_VALUE;
		}
		// Add the protocol to the new contact
		if( CallService( MS_PROTO_ADDTOCONTACT, ( WPARAM )_hContact, ( LPARAM )proto() ) ) {
			CallService( MS_DB_CONTACT_DELETE, ( WPARAM )_hContact, 0 );
			_hContact = INVALID_HANDLE_VALUE;
			return INVALID_HANDLE_VALUE;
		}
		// write uid to protocol module
		cws.szModule = proto();
		cws.szSetting = uidk();
		cws.value = _dbvUID;
		if( CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM )_hContact, ( LPARAM )&cws ) ) {
			CallService( MS_DB_CONTACT_DELETE, ( WPARAM )_hContact, 0 );
			_hContact = INVALID_HANDLE_VALUE;
			return INVALID_HANDLE_VALUE;
		}
		// write nick and display name
		if( nick() ) DB::Setting::WriteUString( _hContact, proto(), SET_CONTACT_NICK, ( LPSTR )nick() );
		if( disp() ) DB::Setting::WriteUString( _hContact, MOD_CLIST, SET_CONTACT_MYHANDLE, ( LPSTR )disp() );

		// add group
		if( group() ) {
			DB::Setting::WriteUString( _hContact, MOD_CLIST, "Group", ( LPSTR )group() );
			if( CListFindGroup( group() ) == INVALID_HANDLE_VALUE ) {
				WPARAM hGroup = ( WPARAM )CallService( MS_CLIST_GROUPCREATE, 0, 0 );
				if( hGroup ) {
					// renaming twice is stupid but the only way to avoid error dialog telling shit like
					// a group with that name does exist
					CallService( MS_CLIST_GROUPRENAME, hGroup, ( LPARAM )group() );
					//CallService( MS_CLIST_GROUPRENAME, hGroup, ( LPARAM )group() );
				}
			}
		}
	}
	return _hContact;
}

/**
 * name:	toIni
 * class:	CExImContactBase
 * desc:	writes the line to an opened ini file which identifies the contact
 *			whose information are stored in this class
 * param:	file	- pointer to the opened file
 * return:	nothing
 **/
VOID CExImContactBase::toIni( FILE* file )
{
	if( strlen(_strProto) > 0 ) {
		LPCSTR pszUID = uid2String( FALSE );

		if( strlen(_strUIDKey) > 0 && pszUID ) {
			fprintf( file, "CONTACT: %s *( %s )*<%s>*{%s}*", 
				strlen(_strNick) > 0 ? _strNick : "( UNKNOWN )",
				_strProto, _strUIDKey, pszUID );
		}
		else {
			fprintf( file, "FROM CONTACT: %s ( %s )", 
				strlen(_strNick) > 0 ? _strNick : "( UNKNOWN )",
				_strProto );
		}
	}
	else {
		fprintf( file, "FROM CONTACT: %s ", strlen(_strNick) > 0 ? _strNick : "( UNKNOWN )" );
	}
	fputs( "\n\n", file );
}

BOOLEAN CExImContactBase::compareUID( DWORD val, BYTE type )
{
	CHAR buf[32];

	switch( _dbvUID.type ) {
		case DBVT_BYTE:
			if( val == _dbvUID.bVal ) {
				_dbvUID.type = type;
				return TRUE;
			}
			break;
		case DBVT_WORD:
			if( val == _dbvUID.wVal ) {
				_dbvUID.type = type;
				return TRUE;
			}
			break;
		case DBVT_DWORD:
			if( val == _dbvUID.dVal ) {
				_dbvUID.type = type;
				return TRUE;
			}
			break;
		case DBVT_UTF8:
			_itoa( val, buf, 10 );
			if( _dbvUID.pszVal && !mir_stricmp( buf, _dbvUID.pszVal ) ) {
				mir_free( _dbvUID.pszVal );
				_dbvUID.type = type;
				return TRUE;
			}
			break;
	}
	return FALSE;
}

LPSTR CExImContactBase::uid2String( BOOLEAN bPrependType )
{
	CHAR szUID[MAX_PATH];
	LPSTR ptr = szUID;

	switch( _dbvUID.type ) {
		case DBVT_BYTE:
			if( bPrependType ) *ptr++ = 'b';
			_itoa( _dbvUID.bVal, ptr, 10 );
			break;
		case DBVT_WORD:
			if( bPrependType ) *ptr++ = 'w';
			_itoa( _dbvUID.wVal, ptr, 10 );
			break;
		case DBVT_DWORD:
			if( bPrependType ) *ptr++ = 'd';
			_itoa( _dbvUID.dVal, ptr, 10 );
			break;
		case DBVT_UTF8:
			if( bPrependType ) *ptr++ = 'u';
			mir_strncpy( ptr, _dbvUID.pszVal, SIZEOF( szUID )-1 );
			break;
		
		case DBVT_ASCIIZ: {
			LPSTR r = mir_utf8encode( _dbvUID.pszVal );

			if( bPrependType ) {
				LPSTR t = ( LPSTR )mir_alloc( strlen( r )+2 );
				t[0] = 'u';
				strcpy( t+1, r );
				mir_free( r );
				r = t;
			}
			return r;
		}
		
		case DBVT_WCHAR: {
			LPSTR r = mir_utf8encodeW( _dbvUID.pwszVal );

			if( bPrependType ) {
				LPSTR t = ( LPSTR )mir_alloc( strlen( r )+2 );
				t[0] = 'u';
				strcpy( t+1, r );
				mir_free( r );
				r = t;
			}
			return r;
		}
	}
	return mir_strdup( szUID );
}

BOOLEAN CExImContactBase::isMeta() const
{
	return DB::Module::IsMeta( proto() );
}

BOOLEAN CExImContactBase::isHandle( HANDLE hContact )
{
	LPCSTR pszProto;
	DBVARIANT dbv;
	BOOLEAN isEqual = FALSE;

	// owner contact ?
	if( proto() == NULL ) return hContact == NULL;

	// compare protocols
	pszProto = DB::Contact::Proto( hContact );
	if( pszProto == NULL || ( INT )pszProto == CALLSERVICE_NOTFOUND || mir_stricmp( pszProto, proto() ) )
		return FALSE;

	// compare uids
	if( uidk() ) {
		// get uid
		if( DB::Setting::GetUString( hContact, pszProto, uidk(), &dbv ) )
			return FALSE;

		switch( dbv.type ) {
		case DBVT_BYTE:
			if( compareUID( dbv.bVal, dbv.type ) )
				return TRUE;
			break;
		case DBVT_WORD:
			if( compareUID( dbv.wVal, dbv.type ) )
				return TRUE;
			break;
		case DBVT_DWORD:
			if( compareUID( dbv.dVal, dbv.type ) )
				return TRUE;
			break;
		case DBVT_UTF8:
			isEqual = ( _dbvUID.type == dbv.type && !strcmp( _dbvUID.pszVal, dbv.pszVal ) );
			break;
		}
		DB::Variant::Free( &dbv );
	}
	// compare nicknames
	else if( !DB::Setting::GetUString( hContact, proto(), SET_CONTACT_NICK, &dbv ) ) {
		if( dbv.type == DBVT_UTF8 && dbv.pszVal && !mir_stricmp( dbv.pszVal, nick() ) ) {
			INT ans = MsgBox( NULL, MB_ICONQUESTION|MB_YESNO, LPGENT( "Question" ), LPGENT( "contact identificaion" ),
				LPGENT( "The contact %s( %s ) has no unique id in the vCard,\nbut there is a contact in your clist with the same nick and protocol.\nDo you wish to use this contact?" ),
				nick(), proto() );
			isEqual = ans == IDYES;
		}
		DB::Variant::Free( &dbv );
	}
	return isEqual;
}

/**
 * name:	handle
 * class:	CExImContactBase
 * desc:	return the handle of the contact
 *			whose information are stored in this class
 * param:	none
 * return:	handle if successful, INVALID_HANDLE_VALUE otherwise
 **/
HANDLE CExImContactBase::findHandle()
{
	HANDLE hContact;

	for( hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDFIRST, NULL, NULL );
		hContact != NULL;
		hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDNEXT, ( WPARAM )hContact, NULL ) )
	{
		if( isHandle( hContact ) ) {
			_hContact = hContact;
			_isNewContact = FALSE;
			return hContact;
		}
	}
	_isNewContact = TRUE;
	_hContact = INVALID_HANDLE_VALUE;
	return INVALID_HANDLE_VALUE;
}


} // namespace NExImport